package com.smdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.smdp.utils.BlogData;
import com.smdp.dto.Result;
import com.smdp.dto.ScrollResult;
import com.smdp.dto.UserDTO;
import com.smdp.entity.Blog;
import com.smdp.entity.Follow;
import com.smdp.entity.User;
import com.smdp.mapper.BlogMapper;
import com.smdp.service.IBlogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.smdp.service.IFollowService;
import com.smdp.service.IUserService;
import com.smdp.utils.SystemConstants;
import com.smdp.utils.UserHolder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

import static com.smdp.utils.BlogConstants.NEED_SAVE;
import static com.smdp.utils.BlogConstants.NEED_UPDATE;
import static com.smdp.utils.RabbitMQConstants.*;
import static com.smdp.utils.RedisConstants.*;

/**
 * 博客服务实现类。
 * 包含发博客（审核后正式发布）、更新博客（审核后正式更新）、为博客点赞、查询（自己的、根据用户id、热门、根据博客id）博客、删除博客，
 * 以及更新和发布审核通过的博客。
 *
 * @author 蔡方珩
 */
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Resource
    private IUserService userService;

    @Resource
    private IFollowService followService;

    @Resource
    RabbitTemplate rabbitTemplate;

    /**
     * 根据博客id查询博客
     * @param id 博客id
     * @return 成功：博客 / 失败：失败原因
     */
    @Override
    public Result queryBlogById(Long id) {
        // 查询blog
        Blog blog = getById(id);
        if (blog == null) {
            return Result.fail("笔记不存在");
        }
        if (blog.getStatus() != 0){
            return Result.fail("笔记无法被查看");
        }
        // 查询blog相关用户
        queryBlogUser(blog);
        // 查询blog是否被点赞
        isBlogLiked(blog);
        return Result.ok(blog);
    }

    /**
     * 查询热门博客（按点赞数排序）
     * @param current 当前页数
     * @return 成功：分页热门博客
     */
    @Override
    public Result queryHotBlog(Integer current) {
        // 查询
        Page<Blog> page = query()
                .eq("status", 0)
                .orderByDesc("liked")
                .page(new Page<>(current, SystemConstants.BLOG_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        // 查询用户,查询blog是否被点赞
        records.forEach(blog -> {
            this.queryBlogUser(blog);
            this.isBlogLiked(blog);
        });
        return Result.ok(records);
    }

    /**
     * 点赞博客。
     * 如果用户已经点赞则取消点赞。
     * @param id 博客id
     * @return ok
     */
    @Override
    public Result likeBlog(Long id) {
        // 获取登录用户
        Long userId = UserHolder.getUser().getId();
        // 判断是否已经点赞
        String key = BLOG_LIKED_KEY + id;
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        if (score == null) {
            // 如果未点赞：数据库点赞数+1，保存用户到Redis的Set集合
            boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
            }
        } else {
            // 如果已点赞：数据库点赞数-1，将用户从Redis的Set集合移除
            boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().remove(key, userId.toString());
            }
        }
        return Result.ok();
    }

    /**
     * 查询最先点赞博客的5名用户
     * @param id 博客id
     * @return 成功：先点赞博客的5名用户的列表 / 失败：空白列表
     */
    @Override
    public Result queryBlogLikes(Long id) {
        String key = BLOG_LIKED_KEY + id;
        // 查询top5点赞用户
        Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
        if (top5 == null || top5.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }
        // 解析用户id
        List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
        String idStr = StrUtil.join(",", ids);
        // 根据用户id查询用户
        List<UserDTO> userDTOS = userService.query()
                .in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list()
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        // 返回用户信息
        return Result.ok(userDTOS);
    }

    /**
     * 发布审核通过的博客。
     * @param blog 博客对象
     */
    @Override
    public void saveBlogAfterVerify(Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 保存探店博文
        save(blog);
        // 查询作者的所有粉丝
        List<Follow> follows = followService.query()
                .eq("follow_user_id", user.getId())
                .list();
        // 推送笔记id给所有粉丝
        for (Follow follow : follows) {
            // 获取粉丝id
            Long userId = follow.getUserId();
            // 推送
            String key = FEED_KEY + userId;
            stringRedisTemplate.opsForZSet()
                    .add(key, blog.getId().toString(), System.currentTimeMillis());
        }
    }

    /**
     * 查询“我的关注”博主发的博客
     * @param lastId 上次查询的最小时间戳，即本轮查询的、第一条博客的时间戳，是查询的原点
     * @param offset 偏移量，时间戳为lastId、且已查询过的博客数量
     * @return 成功：ScrollResult，储存Blog、minTime、offset / 失败：失败原因
     */
    @Override
    public Result queryBlogOfFollow(Long lastId, Integer offset) {
        // 1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        // 2.查看收件箱
        String key = FEED_KEY + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples =
                stringRedisTemplate.opsForZSet()
                        .reverseRangeByScoreWithScores(key, 0, lastId, offset, FOLLOW_PAGE_SIZE);
        // 3.非空判断
        if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();
        }
        // 4.解析数据:blogId、minTime (时间戳）、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0; // 最后一条blog的时间（minTime，用于分页查询）
        int os = 1; // 同一时间的blog数量（offset，用于分页查询）
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
            // 4.1.获得id
            ids.add(Long.valueOf(tuple.getValue()));
            //4.2.获得分数(时间戳)
            long time = tuple.getScore().longValue();
            if (time == minTime) {
                os++;
            } else {
                minTime = time;
                os = 1;
            }
        }
        // 5.跟据id查询blog
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = query()
                .in("id", ids)
                .last("ORDER BY FIELD(id," + idStr + ")")
                .list();
        for (int i = 0, blogsSize = blogs.size(); i < blogsSize; i++) {
            Blog blog = blogs.get(i);
            if (blog.getStatus() != 0){
                // 删除无法被查看的博客
                blogs.set(i, null);
                continue;
            }
            // 5.1.查blog的发布者
            queryBlogUser(blog);
            // 5.2.查blog是否被点赞
            isBlogLiked(blog);
        }
        // 6.封装并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);
        return Result.ok(r);
    }

    /**
     * 查询我的博客
     *
     * @param current 当前页数
     * @return 分页的我的博客
     */
    @Override
    public Result queryMyBlog(Integer current) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        // 根据用户查询
        Page<Blog> page = query()
                .eq("user_id", user.getId())
                .eq("status", 0)
                .page(new Page<>(current, SystemConstants.BLOG_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        // 查询用户,查询blog是否被点赞
        records.forEach(blog -> {
            this.queryBlogUser(blog);
            this.isBlogLiked(blog);
        });
        return Result.ok(records);
    }

    /**
     * 根据id删除博客
     *
     * @param blogId 博客id
     * @return 成功：blogId / 失败：失败原因
     */
    @Override
    public Result deleteBlogById(Long blogId) {
        Long userId = UserHolder.getUser().getId();
        // 逻辑删除
        boolean isSuccess = update().setSql("status = 3")
                .eq("id", blogId)
                .eq("user_id", userId)
                .update();
        if (!isSuccess) {
            return Result.fail("删除失败，没有此博客，或该博客不是你发布的！");
        }
        return Result.ok(blogId);
    }

    /**
     * 更新审核通过的博客
     *
     * @param blog 博客对象
     */
    @Override
    public void updateBlogAfterVerify(Blog blog) {
        // 身份校验
        Long userId = UserHolder.getUser().getId();
        if (!Objects.equals(blog.getUserId(), userId)) {
            log.error("只有发布者可以更新博客！");
            return;
        }
        // 查询博客
        Long blogId = blog.getId();
        Blog oldBlog = getOne(new QueryWrapper<Blog>()
                .eq("id", blogId)
                .eq("user_id", userId)
        );
        // 检验博客是否存在，是否被删除/举报
        if (oldBlog == null || oldBlog.getStatus() != 0) {
            log.error("更新失败，没有此博客，或该博客不是你发布的！");
            return;
        }
        // 更新博客
        boolean isSuccess = updateById(blog);
        if (!isSuccess) {
            log.error("id为" + blog.getId() + "的博客更新失败");
        }
    }

    /**
     * 发博客，将博客推送到MQ等待审核，审核通过后发布。
     *
     * @param blog 博客对象
     * @return ok
     */
    @Override
    public Result saveBlog(Blog blog) throws Exception {
        // 将blog和save信息转化为BlogWithStatus对象
        BlogData blogData = new BlogData();
        blogData.setBlog(blog);
        blogData.setStatus(NEED_SAVE);
        // 转化为JSON数据
        String json = getJsonFromObject(blogData);
        // 发送到MQ
        rabbitTemplate.convertAndSend(VERIFY_EXCHANGE,VERIFY_ROUTE_KEY,json);
        return Result.ok();
    }

    /**
     * 更新博客，将博客推送到MQ等待审核，审核通过后更新。
     *
     * @param blog 博客对象
     * @return ok
     */
    @Override
    public Result updateBlog(Blog blog) throws Exception {
        // 将blog和save信息转化为BlogWithStatus对象
        BlogData blogData = new BlogData();
        blogData.setBlog(blog);
        blogData.setStatus(NEED_UPDATE);
        // 转化为JSON数据
        String json = getJsonFromObject(blogData);
        // 发送到MQ
        rabbitTemplate.convertAndSend(VERIFY_EXCHANGE,VERIFY_ROUTE_KEY,json);
        return Result.ok();
    }

    /**
     * 根据用户id查询博客
     *
     * @param current 当前页数
     * @param userId  用户id
     * @return 分页的用户博客
     */
    @Override
    public Result queryBlogByUserId(Integer current, Long userId) {
        // 根据用户查询
        Page<Blog> page = query()
                .eq("user_id", userId)
                .eq("status", 0)
                .page(new Page<>(current, SystemConstants.BLOG_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        // 查询用户,查询blog是否被点赞
        records.forEach(blog -> {
            this.queryBlogUser(blog);
            this.isBlogLiked(blog);
        });
        return Result.ok(records);
    }

    /**
     * 将对象转化为JSON数据
     *
     * @param obj 被转化的对象
     * @return 对象不为null：转化后的JSON数据 / 对象为null：null
     */
    public String getJsonFromObject(Serializable obj) throws Exception {
        if (obj == null) {
            return null;
        }
        //将Java对象匹配JSON结构
        ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(obj);
    }

    /**
     * 查询用户是否给博客点赞，并保存到博客对象中
     *
     * @param blog 博客
     */
    private void isBlogLiked(Blog blog) {
        UserDTO user = UserHolder.getUser();
        if (user == null) {
            // 用户未登录，无需查询是否点赞
            return;
        }
        Long userId = user.getId();
        String key = "blog:like:" + blog.getId();
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        blog.setIsLike(score != null);
    }

    /**
     * 查询博客的发布者，并保存到博客对象中
     *
     * @param blog 博客
     */
    private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }
}
